﻿using System;
using System.Reflection;
using System.Windows;
using System.Windows.Interactivity;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Utils;


namespace MugenMvvmToolkit.Behaviors
{
    /// <summary>
    /// Represents the class for binding to not bindable property.
    /// </summary>
    public sealed class PropertyManager : TriggerAction<FrameworkElement>
    {
        #region Fields

        private PropertyInfo _currentProperty;
        private bool _bindingUpdating;
        private bool _propertyUpdating;

        #endregion

        #region Dependency properties

        /// <summary>
        /// Identifies the <see cref="Binding" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty BindingProperty =
            DependencyProperty.Register("Binding", typeof(object), typeof(PropertyManager),
                                        new PropertyMetadata((o, args) =>
                                                                 {
                                                                     var propertyManager = o as PropertyManager;
                                                                     if (propertyManager == null ||
                                                                         args.OldValue == args.NewValue) return;
                                                                     GlobalSettings.DefaultThreadManager.InvokeInUi(() => propertyManager.TrySetProperty(args.NewValue));
                                                                 }));

        /// <summary>
        /// Identifies the <see cref="SourceProperty" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty SourcePropertyProperty =
            DependencyProperty.Register("SourceProperty", typeof(string), typeof(PropertyManager),
                                        new PropertyMetadata(default(string)));

        /// <summary>
        /// Binding for property <see cref="SourceProperty"/>.
        /// </summary>
        public object Binding
        {
            get { return GetValue(BindingProperty); }
            set { SetValue(BindingProperty, value); }
        }

        /// <summary>
        /// Name property to bind.
        /// </summary>
        public string SourceProperty
        {
            get { return (string)GetValue(SourcePropertyProperty); }
            set { SetValue(SourcePropertyProperty, value); }
        }

        #endregion

        #region Overrides of TriggerAction

        /// <summary>
        /// Invokes the action.
        /// </summary>
        /// <param name="parameter">The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference.</param>
        protected override void Invoke(object parameter)
        {
            GlobalSettings.DefaultThreadManager.InvokeInUi(TrySetBinding);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Invoke this trigger programatically with any event parameter.
        /// </summary>
        public void Execute()
        {
            Invoke(null);
        }

        /// <summary>
        /// Tries to set binding value.
        /// </summary>
        private void TrySetBinding()
        {
            if (_propertyUpdating) return;
            PropertyInfo propertyInfo = GetPropertyInfo();
            if (propertyInfo == null) return;
            if (!propertyInfo.CanRead)
                return;
            _bindingUpdating = true;
            try
            {
                Binding = propertyInfo.GetValue(AssociatedObject, null);
            }
            finally
            {
                _bindingUpdating = false;
            }
        }

        /// <summary>
        /// Tries to set property value.
        /// </summary>
        private void TrySetProperty(object value)
        {
            if (_bindingUpdating) return;
            PropertyInfo propertyInfo = GetPropertyInfo();
            if (propertyInfo == null) return;
            if (!propertyInfo.CanWrite)
                return;
            _propertyUpdating = true;
            try
            {
                propertyInfo.SetValue(AssociatedObject, value, null);
            }
            finally
            {
                _propertyUpdating = false;
            }
        }

        private PropertyInfo GetPropertyInfo()
        {
            if (_currentProperty != null && _currentProperty.Name == SourceProperty)
                return _currentProperty;
            Should.PropertyBeNotNull(AssociatedObject, "AssociatedObject");
            Should.NotBeNullOrWhitespace(SourceProperty, "SourceProperty");
            _currentProperty = AssociatedObject
                .GetType()
                .GetProperty(SourceProperty);
            Should.BeValid(_currentProperty, "SourceProperty", info => info != null);
            return _currentProperty;
        }

        #endregion
    }
}